Widget.extend.close   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 9
rs 9.6666
cc 1
nc 1
nop 0
1
if (typeof(PhpDebugBar) == 'undefined') {
0 ignored issues
show
Bug introduced by
The variable PhpDebugBar seems to be never initialized.
Loading history...
2
    // namespace
3
    var PhpDebugBar = {};
4
    PhpDebugBar.$ = jQuery;
5
}
6
7
(function($) {
8
9
    if (typeof(localStorage) == 'undefined') {
0 ignored issues
show
Bug introduced by
The variable localStorage seems to be never declared. If this is a global, consider adding a /** global: localStorage */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
10
        // provide mock localStorage object for dumb browsers
11
        localStorage = {
0 ignored issues
show
Bug introduced by
The variable localStorage seems to be never declared. Assigning variables without defining them first makes them global. If this was intended, consider making it explicit like using window.localStorage.
Loading history...
12
            setItem: function(key, value) {},
0 ignored issues
show
Unused Code introduced by
The parameter value is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter key is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
13
            getItem: function(key) { return null; }
0 ignored issues
show
Unused Code introduced by
The parameter key is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
14
        };
15
    }
16
17
    if (typeof(PhpDebugBar.utils) == 'undefined') {
0 ignored issues
show
Bug introduced by
The variable PhpDebugBar does not seem to be initialized in case typeof PhpDebugBar == "undefined" on line 1 is false. Are you sure this can never be the case?
Loading history...
18
        PhpDebugBar.utils = {};
19
    }
20
21
    /**
22
     * Returns the value from an object property.
23
     * Using dots in the key, it is possible to retrieve nested property values
24
     *
25
     * @param {Object} dict
26
     * @param {String} key
27
     * @param {Object} default_value
28
     * @return {Object}
29
     */
30
    var getDictValue = PhpDebugBar.utils.getDictValue = function(dict, key, default_value) {
31
        var d = dict, parts = key.split('.');
32
        for (var i = 0; i < parts.length; i++) {
33
            if (!d[parts[i]]) {
34
                return default_value;
35
            }
36
            d = d[parts[i]];
37
        }
38
        return d;
39
    }
40
41
    /**
42
     * Counts the number of properties in an object
43
     *
44
     * @param {Object} obj
45
     * @return {Integer}
46
     */
47
    var getObjectSize = PhpDebugBar.utils.getObjectSize = function(obj) {
0 ignored issues
show
Bug introduced by
The variable PhpDebugBar does not seem to be initialized in case typeof PhpDebugBar == "undefined" on line 1 is false. Are you sure this can never be the case?
Loading history...
48
        if (Object.keys) {
49
            return Object.keys(obj).length;
50
        }
51
        var count = 0;
52
        for (var k in obj) {
53
            if (obj.hasOwnProperty(k)) {
54
                count++;
55
            }
56
        }
57
        return count;
58
    }
59
60
    /**
61
     * Returns a prefixed css class name
62
     *
63
     * @param {String} cls
64
     * @return {String}
65
     */
66
    PhpDebugBar.utils.csscls = function(cls, prefix) {
0 ignored issues
show
Bug introduced by
The variable PhpDebugBar does not seem to be initialized in case typeof PhpDebugBar == "undefined" on line 1 is false. Are you sure this can never be the case?
Loading history...
67
        if (cls.indexOf(' ') > -1) {
68
            var clss = cls.split(' '), out = [];
69
            for (var i = 0, c = clss.length; i < c; i++) {
70
                out.push(PhpDebugBar.utils.csscls(clss[i], prefix));
0 ignored issues
show
Bug introduced by
The variable PhpDebugBar does not seem to be initialized in case typeof PhpDebugBar == "undefined" on line 1 is false. Are you sure this can never be the case?
Loading history...
71
            }
72
            return out.join(' ');
73
        }
74
        if (cls.indexOf('.') === 0) {
75
            return '.' + prefix + cls.substr(1);
76
        }
77
        return prefix + cls;
78
    };
79
80
    /**
81
     * Creates a partial function of csscls where the second
82
     * argument is already defined
83
     *
84
     * @param  {string} prefix
85
     * @return {Function}
86
     */
87
    PhpDebugBar.utils.makecsscls = function(prefix) {
88
        var f = function(cls) {
89
            return PhpDebugBar.utils.csscls(cls, prefix);
0 ignored issues
show
Bug introduced by
The variable PhpDebugBar does not seem to be initialized in case typeof PhpDebugBar == "undefined" on line 1 is false. Are you sure this can never be the case?
Loading history...
90
        };
91
        return f;
92
    }
93
94
    var csscls = PhpDebugBar.utils.makecsscls('phpdebugbar-');
95
96
97
    // ------------------------------------------------------------------
98
    
99
    /**
100
     * Base class for all elements with a visual component
101
     *
102
     * @param {Object} options
103
     * @constructor
104
     */
105
    var Widget = PhpDebugBar.Widget = function(options) {
106
        this._attributes = $.extend({}, this.defaults);
107
        this._boundAttributes = {};
108
        this.$el = $('<' + this.tagName + ' />');
109
        if (this.className) {
110
            this.$el.addClass(this.className);
111
        }
112
        this.initialize.apply(this, [options || {}]);
113
        this.render.apply(this);
114
    };
115
116
    $.extend(Widget.prototype, {
117
118
        tagName: 'div',
119
120
        className: null,
121
122
        defaults: {},
123
124
        /**
125
         * Called after the constructor
126
         * 
127
         * @param {Object} options
128
         */
129
        initialize: function(options) {
130
            this.set(options);
131
        },
132
133
        /**
134
         * Called after the constructor to render the element
135
         */
136
        render: function() {},
137
138
        /**
139
         * Sets the value of an attribute
140
         * 
141
         * @param {String} attr Can also be an object to set multiple attributes at once
142
         * @param {Object} value
143
         */
144
        set: function(attr, value) {
145
            if (typeof(attr) != 'string') {
146
                for (var k in attr) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
147
                    this.set(k, attr[k]);
148
                }
149
                return;
150
            }
151
152
            this._attributes[attr] = value;
153
            if (typeof(this._boundAttributes[attr]) !== 'undefined') {
154
                for (var i = 0, c = this._boundAttributes[attr].length; i < c; i++) {
155
                    this._boundAttributes[attr][i].apply(this, [value]);
156
                }
157
            }
158
        },
159
160
        /**
161
         * Checks if an attribute exists and is not null
162
         * 
163
         * @param {String} attr
164
         * @return {[type]} [description]
165
         */
166
        has: function(attr) {
167
            return typeof(this._attributes[attr]) !== 'undefined' && this._attributes[attr] !== null;
168
        },
169
170
        /**
171
         * Returns the value of an attribute
172
         * 
173
         * @param {String} attr
174
         * @return {Object}
175
         */
176
        get: function(attr) {
177
            return this._attributes[attr];
178
        },
179
180
        /**
181
         * Registers a callback function that will be called whenever the value of the attribute changes
182
         *
183
         * If cb is a jQuery element, text() will be used to fill the element
184
         * 
185
         * @param {String} attr
186
         * @param {Function} cb
187
         */
188
        bindAttr: function(attr, cb) {
189
            if ($.isArray(attr)) {
190
                for (var i = 0, c = attr.length; i < c; i++) {
191
                    this.bindAttr(attr[i], cb);
192
                }
193
                return;
194
            }
195
196
            if (typeof(this._boundAttributes[attr]) == 'undefined') {
197
                this._boundAttributes[attr] = [];
198
            }
199
            if (typeof(cb) == 'object') {
200
                var el = cb;
201
                cb = function(value) { el.text(value || ''); };
202
            }
203
            this._boundAttributes[attr].push(cb);
204
            if (this.has(attr)) {
205
                cb.apply(this, [this._attributes[attr]]);
206
            }
207
        }
208
209
    });
210
211
212
    /**
213
     * Creates a subclass
214
     *
215
     * Code from Backbone.js
216
     * 
217
     * @param {Array} props Prototype properties
218
     * @return {Function}
219
     */
220
    Widget.extend = function(props) {
221
        var parent = this;
222
223
        var child = function() { return parent.apply(this, arguments); };
224
        $.extend(child, parent);
225
226
        var Surrogate = function(){ this.constructor = child; };
227
        Surrogate.prototype = parent.prototype;
228
        child.prototype = new Surrogate;
229
        $.extend(child.prototype, props);
230
231
        child.__super__ = parent.prototype;
232
233
        return child;
234
    };
235
236
    // ------------------------------------------------------------------
237
238
    /**
239
     * Tab
240
     * 
241
     * A tab is composed of a tab label which is always visible and
242
     * a tab panel which is visible only when the tab is active.
243
     *
244
     * The panel must contain a widget. A widget is an object which has
245
     * an element property containing something appendable to a jQuery object.
246
     *
247
     * Options:
248
     *  - title
249
     *  - badge
250
     *  - widget
251
     *  - data: forward data to widget data
252
     */
253
    var Tab = Widget.extend({
254
255
        className: csscls('panel'),
256
257
        render: function() {
258
            this.$tab = $('<a />').addClass(csscls('tab'));
259
260
            this.$icon = $('<i />').appendTo(this.$tab);
261
            this.bindAttr('icon', function(icon) {
262
                if (icon) {
263
                    this.$icon.attr('class', 'phpdebugbar-fa phpdebugbar-fa-' + icon);
264
                } else {
265
                    this.$icon.attr('class', '');
266
                }
267
            });
268
269
            this.bindAttr('title', $('<span />').addClass(csscls('text')).appendTo(this.$tab));
270
271
            this.$badge = $('<span />').addClass(csscls('badge')).appendTo(this.$tab);
272
            this.bindAttr('badge', function(value) {
273
                if (value !== null) {
274
                    this.$badge.text(value);
275
                    this.$badge.show();
276
                } else {
277
                    this.$badge.hide();
278
                }
279
            });
280
281
            this.bindAttr('widget', function(widget) {
282
                this.$el.empty().append(widget.$el);
283
            });
284
285
            this.bindAttr('data', function(data) {
286
                if (this.has('widget')) {
287
                    this.get('widget').set('data', data);
288
                }
289
            })
290
        }
291
292
    });
293
294
    // ------------------------------------------------------------------
295
296
    /**
297
     * Indicator
298
     *
299
     * An indicator is a text and an icon to display single value information
300
     * right inside the always visible part of the debug bar
301
     *
302
     * Options:
303
     *  - icon
304
     *  - title
305
     *  - tooltip
306
     *  - data: alias of title
307
     */
308
    var Indicator = Widget.extend({
309
310
        tagName: 'span',
311
312
        className: csscls('indicator'),
313
314
        render: function() {
315
            this.$icon = $('<i />').appendTo(this.$el);
316
            this.bindAttr('icon', function(icon) {
317
                if (icon) {
318
                    this.$icon.attr('class', 'phpdebugbar-fa phpdebugbar-fa-' + icon);
319
                } else {
320
                    this.$icon.attr('class', '');
321
                }
322
            });
323
324
            this.bindAttr(['title', 'data'], $('<span />').addClass(csscls('text')).appendTo(this.$el));
325
326
            this.$tooltip = $('<span />').addClass(csscls('tooltip disabled')).appendTo(this.$el);
327
            this.bindAttr('tooltip', function(tooltip) {
328
                if (tooltip) {
329
                    this.$tooltip.text(tooltip).removeClass(csscls('disabled'));
330
                } else {
331
                    this.$tooltip.addClass(csscls('disabled'));
332
                }
333
            });
334
        }
335
336
    });
337
338
    // ------------------------------------------------------------------
339
340
    /**
341
     * Dataset title formater
342
     *
343
     * Formats the title of a dataset for the select box
344
     */
345
    var DatasetTitleFormater = PhpDebugBar.DatasetTitleFormater = function(debugbar) {
0 ignored issues
show
Bug introduced by
The variable PhpDebugBar does not seem to be initialized in case typeof PhpDebugBar == "undefined" on line 1 is false. Are you sure this can never be the case?
Loading history...
346
        this.debugbar = debugbar;
347
    };
348
349
    $.extend(DatasetTitleFormater.prototype, {
350
351
        /**
352
         * Formats the title of a dataset
353
         * 
354
         * @this {DatasetTitleFormater}
355
         * @param {String} id
356
         * @param {Object} data
357
         * @param {String} suffix
358
         * @return {String}
359
         */
360
        format: function(id, data, suffix) {
361
            if (suffix) {
362
                suffix = ' ' + suffix;
363
            } else {
364
                suffix = '';
365
            }
366
367
            var nb = getObjectSize(this.debugbar.datasets) + 1;
368
369
            if (typeof(data['__meta']) === 'undefined') {
370
                return "#" + nb + suffix;
371
            }
372
373
            var filename = data['__meta']['uri'].substr(data['__meta']['uri'].lastIndexOf('/') + 1);
374
            var label = "#" + nb + " " + filename + suffix + ' (' + data['__meta']['datetime'].split(' ')[1] + ')';
375
            return label;
376
        }
377
378
    });
379
380
    // ------------------------------------------------------------------
381
382
383
    /**
384
     * DebugBar
385
     *
386
     * Creates a bar that appends itself to the body of your page
387
     * and sticks to the bottom.
388
     *
389
     * The bar can be customized by adding tabs and indicators.
390
     * A data map is used to fill those controls with data provided
391
     * from datasets.
392
     */
393
    var DebugBar = PhpDebugBar.DebugBar = Widget.extend({
0 ignored issues
show
Bug introduced by
The variable PhpDebugBar does not seem to be initialized in case typeof PhpDebugBar == "undefined" on line 1 is false. Are you sure this can never be the case?
Loading history...
394
395
        className: "phpdebugbar " + csscls('minimized'),
396
397
        options: {
398
            bodyMarginBottom: true,
399
            bodyMarginBottomHeight: parseInt($('body').css('margin-bottom'))
400
        },
401
402
        initialize: function() {
403
            this.controls = {};
404
            this.dataMap = {};
405
            this.datasets = {};
406
            this.firstTabName = null;
407
            this.activePanelName = null;
408
            this.datesetTitleFormater = new DatasetTitleFormater(this);
409
            this.registerResizeHandler();
410
        },
411
412
        /**
413
         * Register resize event, for resize debugbar with reponsive css.
414
         *
415
         * @this {DebugBar}
416
         */
417
        registerResizeHandler: function() {
418
            if (typeof this.resize.bind == 'undefined') return;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
419
420
            var f = this.resize.bind(this);
421
            this.respCSSSize = 0;
422
            $(window).resize(f);
423
            setTimeout(f, 20);
424
        },
425
426
        /**
427
         * Resizes the debugbar to fit the current browser window
428
         */
429
        resize: function() {
430
            var contentSize = this.respCSSSize;
431
            if (this.respCSSSize == 0) {
432
                this.$header.find("> div > *:visible").each(function () {
433
                    contentSize += $(this).outerWidth();
434
                });
435
            }
436
437
            var currentSize = this.$header.width();
438
            var cssClass = "phpdebugbar-mini-design";
439
            var bool = this.$header.hasClass(cssClass);
440
441
            if (currentSize <= contentSize && !bool) {
442
                this.respCSSSize = contentSize;
443
                this.$header.addClass(cssClass);
444
            } else if (contentSize < currentSize && bool) {
445
                this.respCSSSize = 0;
446
                this.$header.removeClass(cssClass);
447
            }
448
449
            // Reset height to ensure bar is still visible
450
            this.setHeight(this.$body.height());
451
        },
452
453
        /**
454
         * Initialiazes the UI
455
         *
456
         * @this {DebugBar}
457
         */
458
        render: function() {
459
            var self = this;
460
            this.$el.appendTo('body');
461
            this.$dragCapture = $('<div />').addClass(csscls('drag-capture')).appendTo(this.$el);
462
            this.$resizehdle = $('<div />').addClass(csscls('resize-handle')).appendTo(this.$el);
463
            this.$header = $('<div />').addClass(csscls('header')).appendTo(this.$el);
464
            this.$headerLeft = $('<div />').addClass(csscls('header-left')).appendTo(this.$header);
465
            this.$headerRight = $('<div />').addClass(csscls('header-right')).appendTo(this.$header);
466
            var $body = this.$body = $('<div />').addClass(csscls('body')).appendTo(this.$el);
467
            this.recomputeBottomOffset();
468
469
            // dragging of resize handle
470
            var pos_y, orig_h;
471
            this.$resizehdle.on('mousedown', function(e) {
472
                orig_h = $body.height(), pos_y = e.pageY;
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
473
                $body.parents().on('mousemove', mousemove).on('mouseup', mouseup);
474
                self.$dragCapture.show();
475
                e.preventDefault();
476
            });
477
            var mousemove = function(e) {
478
                var h = orig_h + (pos_y - e.pageY);
479
                self.setHeight(h);
480
            };
481
            var mouseup = function() {
482
                $body.parents().off('mousemove', mousemove).off('mouseup', mouseup);
483
                self.$dragCapture.hide();
484
            };
485
486
            // close button
487
            this.$closebtn = $('<a />').addClass(csscls('close-btn')).appendTo(this.$headerRight);
488
            this.$closebtn.click(function() {
489
                self.close();
490
            });
491
492
            // minimize button
493
            this.$minimizebtn = $('<a />').addClass(csscls('minimize-btn') ).appendTo(this.$headerRight);
494
            this.$minimizebtn.click(function() {
495
                self.minimize();
496
            });
497
498
            // maximize button
499
            this.$maximizebtn = $('<a />').addClass(csscls('maximize-btn') ).appendTo(this.$headerRight);
500
            this.$maximizebtn.click(function() {
501
                self.restore();
502
            });
503
504
            // restore button
505
            this.$restorebtn = $('<a />').addClass(csscls('restore-btn')).hide().appendTo(this.$el);
506
            this.$restorebtn.click(function() {
507
                self.restore();
508
            });
509
510
            // open button
511
            this.$openbtn = $('<a />').addClass(csscls('open-btn')).appendTo(this.$headerRight).hide();
512
            this.$openbtn.click(function() {
513
                self.openHandler.show(function(id, dataset) {
514
                    self.addDataSet(dataset, id, "(opened)");
515
                    self.showTab();
516
                });
517
            });
518
519
            // select box for data sets
520
            this.$datasets = $('<select />').addClass(csscls('datasets-switcher')).appendTo(this.$headerRight);
521
            this.$datasets.change(function() {
522
                self.dataChangeHandler(self.datasets[this.value]);
523
                self.showTab();
524
            });
525
        },
526
527
        /**
528
         * Sets the height of the debugbar body section
529
         * Forces the height to lie within a reasonable range
530
         * Stores the height in local storage so it can be restored
531
         * Resets the document body bottom offset
532
         *
533
         * @this {DebugBar}
534
         */
535
        setHeight: function(height) {
536
          var min_h = 40;
537
          var max_h = $(window).innerHeight() - this.$header.height() - 10;
538
          height = Math.min(height, max_h);
539
          height = Math.max(height, min_h);
540
          this.$body.css('height', height);
541
          localStorage.setItem('phpdebugbar-height', height);
0 ignored issues
show
Bug introduced by
The variable localStorage does not seem to be initialized in case typeof localStorage == "undefined" on line 9 is false. Are you sure this can never be the case?
Loading history...
542
          this.recomputeBottomOffset();
543
        },
544
545
        /**
546
         * Restores the state of the DebugBar using localStorage
547
         * This is not called by default in the constructor and
548
         * needs to be called by subclasses in their init() method
549
         *
550
         * @this {DebugBar}
551
         */
552
        restoreState: function() {
553
            // bar height
554
            var height = localStorage.getItem('phpdebugbar-height');
0 ignored issues
show
Bug introduced by
The variable localStorage does not seem to be initialized in case typeof localStorage == "undefined" on line 9 is false. Are you sure this can never be the case?
Loading history...
555
            this.setHeight(height || this.$body.height());
556
557
            // bar visibility
558
            var open = localStorage.getItem('phpdebugbar-open');
559
            if (open && open == '0') {
560
                this.close();
561
            } else {
562
                var visible = localStorage.getItem('phpdebugbar-visible');
563
                if (visible && visible == '1') {
564
                    var tab = localStorage.getItem('phpdebugbar-tab');
565
                    if (this.isTab(tab)) {
566
                        this.showTab(tab);
567
                    }
568
                }
569
            }
570
        },
571
572
        /**
573
         * Creates and adds a new tab
574
         *
575
         * @this {DebugBar}
576
         * @param {String} name Internal name
577
         * @param {Object} widget A widget object with an element property
578
         * @param {String} title The text in the tab, if not specified, name will be used
579
         * @return {Tab}
580
         */
581
        createTab: function(name, widget, title) {
582
            var tab = new Tab({
583
                title: title || (name.replace(/[_\-]/g, ' ').charAt(0).toUpperCase() + name.slice(1)), 
584
                widget: widget
585
            });
586
            return this.addTab(name, tab);
587
        },
588
589
        /**
590
         * Adds a new tab
591
         *
592
         * @this {DebugBar}
593
         * @param {String} name Internal name
594
         * @param {Tab} tab Tab object
595
         * @return {Tab}
596
         */
597
        addTab: function(name, tab) {
598
            if (this.isControl(name)) {
599
                throw new Error(name + ' already exists');
600
            }
601
602
            var self = this;
603
            tab.$tab.appendTo(this.$headerLeft).click(function() {
604
                if (!self.isMinimized() && self.activePanelName == name) {
605
                    self.minimize();
606
                } else {
607
                    self.showTab(name);
608
                }
609
            });
610
            tab.$el.appendTo(this.$body);
611
612
            this.controls[name] = tab;
613
            if (this.firstTabName == null) {
614
                this.firstTabName = name;
615
            }
616
            return tab;
617
        },
618
619
        /**
620
         * Creates and adds an indicator
621
         *
622
         * @this {DebugBar}
623
         * @param {String} name Internal name
624
         * @param {String} icon
625
         * @param {String} tooltip
626
         * @param {String} position "right" or "left", default is "right"
627
         * @return {Indicator}
628
         */
629
        createIndicator: function(name, icon, tooltip, position) {
630
            var indicator = new Indicator({
631
                icon: icon,
632
                tooltip: tooltip
633
            });
634
            return this.addIndicator(name, indicator, position);
635
        },
636
637
        /**
638
         * Adds an indicator
639
         * 
640
         * @this {DebugBar}
641
         * @param {String} name Internal name
642
         * @param {Indicator} indicator Indicator object
643
         * @return {Indicator}
644
         */
645
        addIndicator: function(name, indicator, position) {
646
            if (this.isControl(name)) {
647
                throw new Error(name + ' already exists');
648
            }
649
650
            if (position == 'left') {
651
                indicator.$el.insertBefore(this.$headerLeft.children().first());
652
            } else {
653
                indicator.$el.appendTo(this.$headerRight);
654
            }
655
656
            this.controls[name] = indicator;
657
            return indicator;
658
        },
659
660
        /**
661
         * Returns a control
662
         * 
663
         * @param {String} name
664
         * @return {Object}
665
         */
666
        getControl: function(name) {
667
            if (this.isControl(name)) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if this.isControl(name) is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
668
                return this.controls[name];
669
            }
670
        },
671
672
        /**
673
         * Checks if there's a control under the specified name
674
         * 
675
         * @this {DebugBar}
676
         * @param {String} name
677
         * @return {Boolean}
678
         */
679
        isControl: function(name) {
680
            return typeof(this.controls[name]) != 'undefined';
681
        },
682
683
        /**
684
         * Checks if a tab with the specified name exists
685
         * 
686
         * @this {DebugBar}
687
         * @param {String} name
688
         * @return {Boolean}
689
         */
690
        isTab: function(name) {
691
            return this.isControl(name) && this.controls[name] instanceof Tab;
692
        },
693
694
        /**
695
         * Checks if an indicator with the specified name exists
696
         * 
697
         * @this {DebugBar}
698
         * @param {String} name
699
         * @return {Boolean}
700
         */
701
        isIndicator: function(name) {
702
            return this.isControl(name) && this.controls[name] instanceof Indicator;
703
        },
704
705
        /**
706
         * Removes all tabs and indicators from the debug bar and hides it
707
         * 
708
         * @this {DebugBar}
709
         */
710
        reset: function() {
711
            this.minimize();
712
            var self = this;
713
            $.each(this.controls, function(name, control) {
714
                if (self.isTab(name)) {
715
                    control.$tab.remove();
716
                }
717
                control.$el.remove();
718
            });
719
            this.controls = {};
720
        },
721
722
        /**
723
         * Open the debug bar and display the specified tab
724
         * 
725
         * @this {DebugBar}
726
         * @param {String} name If not specified, display the first tab
727
         */
728
        showTab: function(name) {
729
            if (!name) {
730
                if (this.activePanelName) {
731
                    name = this.activePanelName;
732
                } else {
733
                    name = this.firstTabName;
734
                }
735
            }
736
737
            if (!this.isTab(name)) {
738
                throw new Error("Unknown tab '" + name + "'");
739
            }
740
741
            this.$resizehdle.show();
742
            this.$body.show();
743
            this.recomputeBottomOffset();
744
745
            $(this.$header).find('> div > .' + csscls('active')).removeClass(csscls('active'));
746
            $(this.$body).find('> .' + csscls('active')).removeClass(csscls('active'));
747
748
            this.controls[name].$tab.addClass(csscls('active'));
749
            this.controls[name].$el.addClass(csscls('active'));
750
            this.activePanelName = name;
751
752
            this.$el.removeClass(csscls('minimized'));
753
            localStorage.setItem('phpdebugbar-visible', '1');
0 ignored issues
show
Bug introduced by
The variable localStorage does not seem to be initialized in case typeof localStorage == "undefined" on line 9 is false. Are you sure this can never be the case?
Loading history...
754
            localStorage.setItem('phpdebugbar-tab', name);
755
            this.resize();
756
        },
757
758
        /**
759
         * Hide panels and minimize the debug bar
760
         *
761
         * @this {DebugBar}
762
         */
763
        minimize: function() {
764
            this.$header.find('> div > .' + csscls('active')).removeClass(csscls('active'));
765
            this.$body.hide();
766
            this.$resizehdle.hide();
767
            this.recomputeBottomOffset();
768
            localStorage.setItem('phpdebugbar-visible', '0');
0 ignored issues
show
Bug introduced by
The variable localStorage does not seem to be initialized in case typeof localStorage == "undefined" on line 9 is false. Are you sure this can never be the case?
Loading history...
769
            this.$el.addClass(csscls('minimized'));
770
            this.resize();
771
        },
772
773
        /**
774
         * Checks if the panel is minimized
775
         * 
776
         * @return {Boolean}
777
         */
778
        isMinimized: function() {
779
            return this.$el.hasClass(csscls('minimized'));
780
        },
781
        
782
        /**
783
         * Close the debug bar
784
         *
785
         * @this {DebugBar}
786
         */
787
        close: function() {
788
            this.$resizehdle.hide();
789
            this.$header.hide();
790
            this.$body.hide();
791
            this.$restorebtn.show();
792
            localStorage.setItem('phpdebugbar-open', '0');
0 ignored issues
show
Bug introduced by
The variable localStorage does not seem to be initialized in case typeof localStorage == "undefined" on line 9 is false. Are you sure this can never be the case?
Loading history...
793
            this.$el.addClass(csscls('closed'));
794
            this.recomputeBottomOffset();
795
        },
796
        
797
        /**
798
         * Checks if the panel is closed
799
         *
800
         * @return {Boolean}
801
         */
802
        isClosed: function() {
803
            return this.$el.hasClass(csscls('closed'));
804
        },
805
        
806
        /**
807
         * Restore the debug bar
808
         *
809
         * @this {DebugBar}
810
         */
811
        restore: function() {
812
            this.$resizehdle.show();
813
            this.$header.show();
814
            this.$restorebtn.hide();
815
            localStorage.setItem('phpdebugbar-open', '1');
0 ignored issues
show
Bug introduced by
The variable localStorage does not seem to be initialized in case typeof localStorage == "undefined" on line 9 is false. Are you sure this can never be the case?
Loading history...
816
            var tab = localStorage.getItem('phpdebugbar-tab');
817
            if (this.isTab(tab)) {
818
                this.showTab(tab);
819
            }
820
            this.$el.removeClass(csscls('closed'));
821
            this.resize();
822
        },
823
824
        /**
825
         * Recomputes the margin-bottom css property of the body so
826
         * that the debug bar never hides any content
827
         */
828
        recomputeBottomOffset: function() {
829
            if (this.options.bodyMarginBottom) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if this.options.bodyMarginBottom is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
830
                if (this.isClosed()) {
831
                    return $('body').css('margin-bottom', this.options.bodyMarginBottomHeight || '');
832
                }
833
                
834
                var offset = parseInt(this.$el.height()) + this.options.bodyMarginBottomHeight;
835
                $('body').css('margin-bottom', offset);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
836
            }
837
        },
838
839
        /**
840
         * Sets the data map used by dataChangeHandler to populate
841
         * indicators and widgets
842
         *
843
         * A data map is an object where properties are control names.
844
         * The value of each property should be an array where the first
845
         * item is the name of a property from the data object (nested properties
846
         * can be specified) and the second item the default value.
847
         *
848
         * Example:
849
         *     {"memory": ["memory.peak_usage_str", "0B"]}
850
         * 
851
         * @this {DebugBar}
852
         * @param {Object} map
853
         */
854
        setDataMap: function(map) {
855
            this.dataMap = map;
856
        },
857
858
        /**
859
         * Same as setDataMap() but appends to the existing map
860
         * rather than replacing it
861
         *
862
         * @this {DebugBar}
863
         * @param {Object} map
864
         */
865
        addDataMap: function(map) {
866
            $.extend(this.dataMap, map);
867
        },
868
869
        /**
870
         * Resets datasets and add one set of data
871
         *
872
         * For this method to be usefull, you need to specify
873
         * a dataMap using setDataMap()
874
         * 
875
         * @this {DebugBar}
876
         * @param {Object} data
877
         * @return {String} Dataset's id
878
         */
879
        setData: function(data) {
880
            this.datasets = {};
881
            return this.addDataSet(data);
882
        },
883
884
        /**
885
         * Adds a dataset
886
         *
887
         * If more than one dataset are added, the dataset selector
888
         * will be displayed.
889
         * 
890
         * For this method to be usefull, you need to specify
891
         * a dataMap using setDataMap()
892
         * 
893
         * @this {DebugBar}
894
         * @param {Object} data
895
         * @param {String} id The name of this set, optional
896
         * @param {String} suffix
897
         * @return {String} Dataset's id
898
         */
899
        addDataSet: function(data, id, suffix) {
900
            var label = this.datesetTitleFormater.format(id, data, suffix);
901
            id = id || (getObjectSize(this.datasets) + 1);
902
            this.datasets[id] = data;
903
904
            this.$datasets.append($('<option value="' + id + '">' + label + '</option>'));
905
            if (this.$datasets.children().length > 1) {
906
                this.$datasets.show();
907
            }
908
909
            this.showDataSet(id);
910
            return id;
911
        },
912
913
        /**
914
         * Loads a dataset using the open handler
915
         * 
916
         * @param {String} id
917
         */
918
        loadDataSet: function(id, suffix, callback) {
919
            if (!this.openHandler) {
920
                throw new Error('loadDataSet() needs an open handler');
921
            }
922
            var self = this;
923
            this.openHandler.load(id, function(data) {
924
                self.addDataSet(data, id, suffix);
925
                callback && callback(data);
926
            });
927
        },
928
929
        /**
930
         * Returns the data from a dataset
931
         * 
932
         * @this {DebugBar}
933
         * @param {String} id
934
         * @return {Object}
935
         */
936
        getDataSet: function(id) {
937
            return this.datasets[id];
938
        },
939
940
        /**
941
         * Switch the currently displayed dataset
942
         * 
943
         * @this {DebugBar}
944
         * @param {String} id
945
         */
946
        showDataSet: function(id) {
947
            this.dataChangeHandler(this.datasets[id]);
948
            this.$datasets.val(id);
949
        },
950
951
        /**
952
         * Called when the current dataset is modified.
953
         * 
954
         * @this {DebugBar}
955
         * @param {Object} data
956
         */
957
        dataChangeHandler: function(data) {
958
            var self = this;
959
            $.each(this.dataMap, function(key, def) {
960
                var d = getDictValue(data, def[0], def[1]);
961
                if (key.indexOf(':') != -1) {
962
                    key = key.split(':');
963
                    self.getControl(key[0]).set(key[1], d);
964
                } else {
965
                    self.getControl(key).set('data', d);
966
                }
967
            });
968
        },
969
970
        /**
971
         * Sets the handler to open past dataset
972
         * 
973
         * @this {DebugBar}
974
         * @param {object} handler
975
         */
976
        setOpenHandler: function(handler) {
977
            this.openHandler = handler;
978
            if (handler !== null) {
979
                this.$openbtn.show();
980
            } else {
981
                this.$openbtn.hide();
982
            }
983
        },
984
985
        /**
986
         * Returns the handler to open past dataset
987
         * 
988
         * @this {DebugBar}
989
         * @return {object}
990
         */
991
        getOpenHandler: function() {
992
            return this.openHandler;
993
        }
994
995
    });
996
997
    DebugBar.Tab = Tab;
998
    DebugBar.Indicator = Indicator;
999
1000
    // ------------------------------------------------------------------
1001
    
1002
    /**
1003
     * AjaxHandler
1004
     *
1005
     * Extract data from headers of an XMLHttpRequest and adds a new dataset
1006
     */
1007
    var AjaxHandler = PhpDebugBar.AjaxHandler = function(debugbar, headerName) {
0 ignored issues
show
Bug introduced by
The variable PhpDebugBar does not seem to be initialized in case typeof PhpDebugBar == "undefined" on line 1 is false. Are you sure this can never be the case?
Loading history...
1008
        this.debugbar = debugbar;
1009
        this.headerName = headerName || 'phpdebugbar';
1010
    };
1011
1012
    $.extend(AjaxHandler.prototype, {
1013
1014
        /**
1015
         * Handles an XMLHttpRequest
1016
         * 
1017
         * @this {AjaxHandler}
1018
         * @param {XMLHttpRequest} xhr
1019
         * @return {Bool}
1020
         */
1021
        handle: function(xhr) {
1022
            if (!this.loadFromId(xhr)) {
1023
                return this.loadFromData(xhr);
1024
            }
1025
            return true;
1026
        },
1027
1028
        /**
1029
         * Checks if the HEADER-id exists and loads the dataset using the open handler
1030
         * 
1031
         * @param {XMLHttpRequest} xhr
1032
         * @return {Bool}
1033
         */
1034
        loadFromId: function(xhr) {
1035
            var id = this.extractIdFromHeaders(xhr);
1036
            if (id && this.debugbar.openHandler) {
1037
                this.debugbar.loadDataSet(id, "(ajax)");
1038
                return true;
1039
            }
1040
            return false;
1041
        },
1042
1043
        /**
1044
         * Extracts the id from the HEADER-id
1045
         * 
1046
         * @param {XMLHttpRequest} xhr
1047
         * @return {String}
1048
         */
1049
        extractIdFromHeaders: function(xhr) {
1050
            return xhr.getResponseHeader(this.headerName + '-id');
1051
        },
1052
1053
        /**
1054
         * Checks if the HEADER exists and loads the dataset
1055
         * 
1056
         * @param {XMLHttpRequest} xhr
1057
         * @return {Bool}
1058
         */
1059
        loadFromData: function(xhr) {
1060
            var raw = this.extractDataFromHeaders(xhr);
1061
            if (!raw) {
1062
                return false;
1063
            }
1064
1065
            var data = this.parseHeaders(raw);
1066
            if (data.error) {
1067
                throw new Error('Error loading debugbar data: ' + data.error);
1068
            } else if(data.data) {
1069
                this.debugbar.addDataSet(data.data, data.id, "(ajax)");
1070
            }
1071
            return true;
1072
        },
1073
1074
        /**
1075
         * Extract the data as a string from headers of an XMLHttpRequest
1076
         * 
1077
         * @this {AjaxHandler}
1078
         * @param {XMLHttpRequest} xhr
1079
         * @return {string}
1080
         */
1081
        extractDataFromHeaders: function(xhr) {
1082
            var data = xhr.getResponseHeader(this.headerName);
1083
            if (!data) {
1084
                return;
1085
            }
1086
            for (var i = 1;; i++) {
1087
                var header = xhr.getResponseHeader(this.headerName + '-' + i);
1088
                if (!header) {
1089
                    break;
1090
                }
1091
                data += header;
1092
            }
1093
            return decodeURIComponent(data);
1094
        },
1095
1096
        /**
1097
         * Parses the string data into an object
1098
         * 
1099
         * @this {AjaxHandler}
1100
         * @param {string} data
1101
         * @return {string}
1102
         */
1103
        parseHeaders: function(data) {
1104
            return JSON.parse(data);
1105
        },
1106
1107
        /**
1108
         * Attaches an event listener to jQuery.ajaxComplete()
1109
         * 
1110
         * @this {AjaxHandler}
1111
         * @param {jQuery} jq Optional
1112
         */
1113
        bindToJquery: function(jq) {
1114
            var self = this;
1115
            jq(document).ajaxComplete(function(e, xhr, settings) {
1116
                if (!settings.ignoreDebugBarAjaxHandler) {
1117
                    self.handle(xhr);
1118
                }
1119
            });
1120
        },
1121
        
1122
        /**
1123
         * Attaches an event listener to XMLHttpRequest
1124
         * 
1125
         * @this {AjaxHandler}
1126
         */
1127
        bindToXHR: function() {
1128
            var self = this;
1129
            var proxied = XMLHttpRequest.prototype.open;
0 ignored issues
show
Bug introduced by
The variable XMLHttpRequest seems to be never declared. If this is a global, consider adding a /** global: XMLHttpRequest */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
1130
            XMLHttpRequest.prototype.open = function(method, url, async, user, pass) {
0 ignored issues
show
Unused Code introduced by
The parameter async is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter pass is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter user is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
1131
                var xhr = this;
1132
                this.addEventListener("readystatechange", function() {
1133
                    var skipUrl = self.debugbar.openHandler ? self.debugbar.openHandler.get('url') : null;
1134
                    if (xhr.readyState == 4 && url.indexOf(skipUrl) !== 0) {
1135
                        self.handle(xhr);
1136
                    }
1137
                }, false);
1138
                proxied.apply(this, Array.prototype.slice.call(arguments));
1139
            };
1140
        }
1141
1142
    });
1143
1144
})(PhpDebugBar.$);
0 ignored issues
show
Bug introduced by
The variable PhpDebugBar does not seem to be initialized in case typeof PhpDebugBar == "undefined" on line 1 is false. Are you sure this can never be the case?
Loading history...
1145